package com.errol.batchprocessor.employeeaddition.advisorimpl.common;

import com.baomidou.mybatisplus.mapper.Condition;
import com.renjia.cache.GlobalSession;
import com.renjia.dao.CoreAgreementDetailMapper;
import com.renjia.dao.CoreEmployeeCustomerMapper;
import com.renjia.dao.CoreEmployeeMapper;
import com.renjia.dao.CoreUserBankMapper;
import com.renjia.entity.CoreEmployee;
import com.renjia.entity.CoreEmployeeCustomer;
import com.renjia.entity.CoreEmployeeImportBatch;
import com.renjia.entity.CoreUserBank;
import com.renjia.enums.*;
import com.renjia.service.CoreEmployeeImportBatchService;
import com.renjia.service.generic.log.LogService;
import com.renjia.service.generic.middleware.batchprocessor.advisorimpl.batchsplitter.BatchSplitter;
import com.renjia.service.generic.middleware.batchprocessor.advisorimpl.batchsplitter.BatchSplitterHelper;
import com.renjia.service.generic.middleware.batchprocessor.advisorimpl.batchsplitter.PieceSizeBatchSplitter;
import com.renjia.service.generic.middleware.batchprocessor.bo.ThreadPoolStatusBO;
import com.renjia.service.generic.middleware.batchprocessor.payload.AbstractBatchPayload;
import com.renjia.service.generic.support.customer.CustomerChannelSupportService;
import com.renjia.service.generic.support.customer.bo.ProtocolAndSignatureBO;
import com.renjia.service.generic.support.employee.BankCardSupportService;
import com.renjia.service.generic.support.employee.EmployeeCustomerSupportService;
import com.renjia.service.generic.support.employee.EmployeeSupportService;
import com.renjia.service.integration.threeelements.ThreeElementsService;
import com.renjia.service.module.employee.AgreementSigningService;
import com.renjia.service.module.employeeaddition.advisor.*;
import com.renjia.service.module.employeeaddition.advisorimpl.EmployeeAdvisorTypeEnum;
import com.renjia.service.module.employeeaddition.advisorimpl.common.payload.AbstractEmployeeBatchPayload;
import com.renjia.service.module.employeeaddition.advisorimpl.common.payload.AbstractEmployeePiecePayload;
import com.renjia.service.module.employeeaddition.advisorimpl.common.payload.AbstractEmployeeSinglePayload;
import com.renjia.util.EpayCallingService;
import com.renjia.utils.DateUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 人员导入批处理的抽象实现，目前有3个入口：人员列表导入、任务接单导入、费用发放导入
 *
 * @author zhang xiao bin
 * @version v1.0
 * @date 2021/03/01
 * @since v5.0.5
 **/
public abstract class AbstractEmployeeAdvisorImpl<BE, SE, BP extends AbstractEmployeeBatchPayload<BE, SE>, PP extends AbstractEmployeePiecePayload<SE, SP>, SP extends AbstractEmployeeSinglePayload<SE>>
        implements BatchSplitterHelper<CoreEmployeeImportBatch, EmployeeAdvisorTypeEnum, BE, SE, BP, PP, SP>,
        EmployeeBatchAddAdvisor<BE, SE, BP, PP, SP>, EmployeeBatchStartAdvisor<BE, SE, BP, PP, SP>, EmployeeBatchFinishAdvisor<BE, SE, BP, PP, SP>, EmployeeBatchPostAdvisor<BE, SE, BP, PP, SP>,
        EmployeePieceStartAdvisor<BE, SE, BP, PP, SP>, EmployeePieceFinishAdvisor<BE, SE, BP, PP, SP>, EmployeePiecePostAdvisor<BE, SE, BP, PP, SP>,
        EmployeeSingleStartAdvisor<BE, SE, BP, PP, SP>, EmployeeSingleProcessAdvisor<BE, SE, BP, PP, SP>, EmployeeSingleFinishAdvisor<BE, SE, BP, PP, SP>, EmployeeSinglePostAdvisor<BE, SE, BP, PP, SP> {

    protected final CoreEmployeeImportBatchService coreEmployeeImportBatchService;
    protected final CoreEmployeeMapper coreEmployeeMapper;
    protected final CoreEmployeeCustomerMapper coreEmployeeCustomerMapper;
    protected final CoreUserBankMapper coreUserBankMapper;
    protected final CoreAgreementDetailMapper coreAgreementDetailMapper;
    protected final ThreeElementsService threeElementsService;
    protected final EmployeeSupportService employeeSupportService;
    protected final EmployeeCustomerSupportService employeeCustomerSupportService;
    protected final BankCardSupportService bankCardSupportService;
    protected final CustomerChannelSupportService customerChannelSupportService;
    protected final AgreementSigningService agreementSigningService;
    protected final EpayCallingService epayCallingService;
    protected final LogService logService;
    protected BatchSplitter<CoreEmployeeImportBatch, EmployeeAdvisorTypeEnum, BE, SE, BP, PP, SP> batchSplitter;

    public AbstractEmployeeAdvisorImpl(CoreEmployeeImportBatchService coreEmployeeImportBatchService,
                                       CoreEmployeeMapper coreEmployeeMapper,
                                       CoreEmployeeCustomerMapper coreEmployeeCustomerMapper,
                                       CoreUserBankMapper coreUserBankMapper,
                                       CoreAgreementDetailMapper coreAgreementDetailMapper,
                                       ThreeElementsService threeElementsService,
                                       EmployeeSupportService employeeSupportService,
                                       EmployeeCustomerSupportService employeeCustomerSupportService,
                                       BankCardSupportService bankCardSupportService,
                                       CustomerChannelSupportService customerChannelSupportService,
                                       AgreementSigningService agreementSigningService,
                                       EpayCallingService epayCallingService,
                                       LogService logService) {
        this.coreEmployeeImportBatchService = coreEmployeeImportBatchService;
        this.coreEmployeeMapper = coreEmployeeMapper;
        this.coreEmployeeCustomerMapper = coreEmployeeCustomerMapper;
        this.coreUserBankMapper = coreUserBankMapper;
        this.coreAgreementDetailMapper = coreAgreementDetailMapper;
        this.threeElementsService = threeElementsService;
        this.employeeSupportService = employeeSupportService;
        this.employeeCustomerSupportService = employeeCustomerSupportService;
        this.bankCardSupportService = bankCardSupportService;
        this.customerChannelSupportService = customerChannelSupportService;
        this.agreementSigningService = agreementSigningService;
        this.epayCallingService = epayCallingService;
        this.logService = logService;
        this.batchSplitter = new PieceSizeBatchSplitter<>(100);
    }

    @Override
    public abstract boolean shallProcess(@NonNull AbstractBatchPayload<CoreEmployeeImportBatch, EmployeeAdvisorTypeEnum, BE, SE> advisorPayload);

    @Override
    public final int priority() {
        // 所有人员导入入口的公共逻辑，每个入口各自继承该抽象类，并实现此中定义的抽象方法
        return 51;
    }

    @Override
    public List<PP> doSplitOnBatchAdded(@NonNull BP batchPayload, @NonNull ThreadPoolStatusBO threadPoolStatus) {
        return batchSplitter.doSplit(batchPayload, threadPoolStatus, this);
    }

    /**
     * 开始处理一个批次，cas 更新 coreEmployeeImportBatch 的导入状态，更新成功才允许处理整个批次
     *
     * @param batchPayload 批次数据
     * @return false 表示更新状态失败，主动拦截该批次，这个批次的数据不会进行任何处理
     */
    @Override
    public boolean doPrepareOnBatchStart(@NonNull BP batchPayload) {
        if (coreEmployeeImportBatchService.updateBatchStatus(batchPayload.getImportBatch(), BatchStatusEnum.IN_THE_QUEUE, BatchStatusEnum.IN_PROCESSING, "")) {
            return
                    // 预加载签约所需信息并检查
                    prepareProtocolAndSignatureBO(batchPayload)
                            // 准备批次内证件信息重复检查所需数据
                            && prepareForDuplicationCheckOfDocument(batchPayload);
        } else {
            return false;
        }
    }

    /**
     * 开启一个分片时执行准备工作
     *
     * @param piecePayload 分片数据
     * @param batchPayload 批次数据
     * @return false 表示主动拦截该分片的执行，该分片的数据将不会执行
     */
    @Override
    public boolean doPrepareOnPieceStart(@NonNull PP piecePayload, @NonNull BP batchPayload) {
        GlobalSession.LOGIN_USER_THREAD_LOCAL.set(batchPayload.getCoreUser());
        // 准备参数校验器
        piecePayload.prepareValidator();
        return true;
    }

    /**
     * 开始处理一条记录时触发，检查该记录状态；批次异常中断重启时，避免重复处理
     *
     * @param singlePayload 单条数据
     * @param piecePayload  分片数据
     * @param batchPayload  批次数据
     * @return false 表示当前记录无需处理
     */
    @Override
    public boolean doPrepareOnSingleStart(@NonNull SP singlePayload, @NonNull PP piecePayload, @NonNull BP batchPayload) {
        return !isSingleEntityProcessed(singlePayload);
    }

    /**
     * 校验人员参数，校验失败不会执行 doExecuteOnSingleProcess 方法
     * 子类需要校验其他参数的，通过继承该方法，先调用 super.doValidateBeforeSingleProcess() 再添加子类的参数校验
     *
     * @param singlePayload 单条数据
     * @param piecePayload  分片数据
     * @param batchPayload  批次数据
     * @return false 表示参数校验失败，导入失败
     */
    @Override
    public boolean doValidateBeforeSingleProcess(@NonNull SP singlePayload, @NonNull PP piecePayload, @NonNull BP batchPayload) {
        //5.3.0添加人员重复过滤
        if (this.allowEmployeeRepeat(singlePayload, piecePayload, batchPayload) || batchPayload.getDocumentMap()
                .get(getDocumentTypeAndNumberString(singlePayload.getSingleEntity())) == singlePayload.getSingleEntity()) {
            // 证件信息指向的就是当前 se，先验证人员信息参数
            if (StringUtils.isEmpty(singlePayload.getEmployeeName()) || singlePayload.getEmployeeName().length() > 40) {
                singlePayload.getErrMsgList().add("姓名不能为空，且不能超过40个字符");
            }
            if (singlePayload.getDocumentTypeEnum() == null || StringUtils.isEmpty(singlePayload.getDocumentNumber())) {
                singlePayload.getErrMsgList().add("证件信息不能为空");
            } else if (DocumentTypeEnum.ID == singlePayload.getDocumentTypeEnum() && !piecePayload.getMyIdNumValidator().isValid(singlePayload.getDocumentNumber(), null)) {
                singlePayload.getErrMsgList().add("身份证号格式不正确");
            }
            // 线上支付额外添加银行卡格式校验
            if (StringUtils.isEmpty(singlePayload.getBankCard())) {
                singlePayload.getErrMsgList().add("收款账号为空");
            }
            if (isMobileNecessary() && StringUtils.isEmpty(singlePayload.getMobile())) {
                singlePayload.getErrMsgList().add("手机号不能为空");
            }
            if (StringUtils.isNotEmpty(singlePayload.getMobile()) && !piecePayload.getMyMobileValidator().isValid(singlePayload.getMobile(), null)) {
                singlePayload.getErrMsgList().add("手机号格式不正确");
            }
            // 发放导入的额外校验通过继承该方法来实现
            return singlePayload.getErrMsgList().isEmpty();
        } else {
            // 批次中证件信息重复，当前 se 验证失败
            singlePayload.getErrMsgList().add("人员重复");
        }
        return false;
    }

    /**
     * 参数校验通过，尝试导入人员信息
     * 该方法是导入人员的核心模板，提供抽象方法给子类进行扩展
     *
     * @param singlePayload 单条数据
     * @param piecePayload  分片数据
     * @param batchPayload  批次数据
     * @return false 表处理失败，将阻止后续同类扩展点的执行
     */
    @Override
    public boolean doExecuteOnSingleProcess(@NonNull SP singlePayload, @NonNull PP piecePayload, @NonNull BP batchPayload) {
        return
                // 先通过证件信息查询人员，不存在则通过手机号查询人员，如果证件号已经重复、手机号已经重复、或手机号关联了其他证件，则验证失败
                // 如果证件信息已存在，但是手机号不同，如果新的手机号未被他人使用，则进行更新
                findEmployeeByDocumentOrMobile(singlePayload, piecePayload, batchPayload)

                        // 根据是否查询到用户，新增或更新用户信息；三要素失败则验证失败；如果用户已存在，则检查 用户-客户、银行卡 是否已存在，如果数据重复，发送提醒，但不会验证失败
                        && dealWithCoreEmployee(singlePayload, piecePayload, batchPayload)

                        // 如果银行卡不存在，且实名已认证，则新增银行卡；如果不是借记卡，验证失败
                        && dealWithCoreUserBank(singlePayload, piecePayload, batchPayload)

                        // 如果 用户-客户 不存在，创建新的 用户-客户 关联记录；如果已存在，且实名未认证，则更新人员信息和认证状态；如果手机号变更，则更新手机号
                        && dealWithCoreEmployeeCustomer(singlePayload, piecePayload, batchPayload)

                        // 如果 用户-客户 未补签、且未生成待签协议，则发起签约
                        && dealWithCoreAgreementDetail(singlePayload, piecePayload, batchPayload);
    }

    /**
     * 人员导入处理完成后进行结算，根据是否有错误信息，分别调用子类更新验证结果的方法
     *
     * @param singlePayload 单条数据
     * @param piecePayload  分片数据
     * @param batchPayload  批次数据
     * @return 除非抛出异常，否则返回 true
     */
    @Override
    public boolean doSettleOnSingleProcessed(@NonNull SP singlePayload, @NonNull PP piecePayload, @NonNull BP batchPayload) {
        if (singlePayload.getErrMsgList().isEmpty()) {
            updateValidateSuccess(singlePayload, piecePayload, batchPayload);
        } else {
            updateValidateFailed(singlePayload, piecePayload, batchPayload, String.join("；", singlePayload.getErrMsgList()));
        }
        return true;
    }

    /**
     * 人员导入异常时，作验证失败处理，也即批次重试时也不会再次处理，需要通过在页面上编辑单条记录的方式处理
     *
     * @param singlePayload 单条数据
     * @param piecePayload  分片数据
     * @param batchPayload  批次数据
     * @param errorMessage  异常描述
     * @return true 除非抛出异常
     */
    @Override
    public boolean doRecoverOnSingleException(@NonNull SP singlePayload, @NonNull PP piecePayload, @NonNull BP batchPayload, String errorMessage) {
        singlePayload.getErrMsgList().add("验证异常");
        updateValidateFailed(singlePayload, piecePayload, batchPayload, String.join("；", singlePayload.getErrMsgList()));
        return true;
    }

    /**
     * 一条人员信息导入结束（正常结或异常重置）后执行
     *
     * @param singlePayload 单条数据
     * @param piecePayload  分片数据
     * @param batchPayload  批次数据
     * @return true
     */
    @Override
    public boolean doCleanUpOnSinglePost(@NonNull SP singlePayload, @NonNull PP piecePayload, @NonNull BP batchPayload) {
        return true;
    }

    /**
     * 分片正常结束时进行结算，费用发放的扩展重写了该方法
     *
     * @param piecePayload 分片数据
     * @param batchPayload 批次数据
     * @return true
     */
    @Override
    public boolean doSettleOnPieceProcessed(@NonNull PP piecePayload, @NonNull BP batchPayload) {
        return true;
    }

    /**
     * 分片异常重置
     *
     * @param piecePayload 分片数据
     * @param batchPayload 批次数据
     * @param errorMessage 异常描述
     * @return true
     */
    @Override
    public boolean doRecoverOnPieceException(@NonNull PP piecePayload, @NonNull BP batchPayload, String errorMessage) {
        return true;
    }

    /**
     * 分片结束后清理缓存
     *
     * @param piecePayload 分片数据
     * @param batchPayload 批次数据
     * @return true
     */
    @Override
    public boolean doCleanUpOnPiecePost(@NonNull PP piecePayload, @NonNull BP batchPayload) {
        GlobalSession.LOGIN_USER_THREAD_LOCAL.remove();
        return true;
    }

    /**
     * 批次所有分片成功导入完成后进行结算，更新 CoreEmployeeImportBatch 的状态
     *
     * @param batchPayload 批次数据
     * @return true 或者抛出异常
     */
    @Override
    public boolean doSettleOnBatchProcessed(@NonNull BP batchPayload) {
        if (coreEmployeeImportBatchService.updateBatchStatus(batchPayload.getImportBatch(), BatchStatusEnum.IN_PROCESSING, BatchStatusEnum.FINISHED, "")) {
            // 批次正常结束
        }
        return true;
    }

    /**
     * 批次存在分片异常时，更新 CoreEmployeeImportBatch 的状态
     *
     * @param batchPayload 批次数据
     * @param errorMessage 异常描述
     * @return true 或者抛出异常
     */
    @Override
    public boolean doRecoverOnBatchException(@NonNull BP batchPayload, String errorMessage) {
        if (coreEmployeeImportBatchService.updateBatchStatus(batchPayload.getImportBatch(), BatchStatusEnum.IN_PROCESSING, BatchStatusEnum.INTERRUPTED, errorMessage)) {
            // 批次异常中断
        }
        return true;
    }

    /**
     * 不管批次导入成功或异常都会执行，检查 CoreEmployeeImportBatch 的状态
     *
     * @param batchPayload 批次数据
     * @return true 或抛出异常
     */
    @Override
    public boolean doCleanUpOnBatchPost(@NonNull BP batchPayload) {
        if (BatchStatusEnum.IN_PROCESSING.getCode().equals(batchPayload.getImportBatch().getBatchStatus())) {
            // 批次启动时 importBatch 状态成功更新，但是 批次启动的其他扩展 主动拦截了该批次，导致批次未运行
            coreEmployeeImportBatchService.updateBatchStatus(batchPayload.getImportBatch(), BatchStatusEnum.IN_PROCESSING, BatchStatusEnum.FINISHED,
                    StringUtils.isEmpty(batchPayload.getImportBatch().getBatchStatusIntro()) ? "批次主动拦截" : batchPayload.getImportBatch().getBatchStatusIntro());
        }
        return true;
    }

    /**
     * 准备用户签约需要的数据，如果是处理单条记录，则应当需要签约时再加载
     *
     * @param batchPayload BP
     * @return false 表示数据未准备时好，拦截批次
     */
    protected boolean prepareProtocolAndSignatureBO(BP batchPayload) {
        if (batchPayload.getProtocolAndSignatureBO() == null) {
            ProtocolAndSignatureBO protocolAndSignature = customerChannelSupportService.getProtocolAndSignatureBy(batchPayload
                    .getCoreCustomerChannelMerchant().getCustomerId(), batchPayload.getCoreCustomerChannelMerchant().getChannelMerchantId());
            if (protocolAndSignature == null || !protocolAndSignature.isSuccess()) {
                logService.remind("导入人员批次对应客户-服务商未配置默认协议或服务商签章，batchNo=" + batchPayload.getBatchNo());
                batchPayload.getImportBatch().setBatchStatusIntro("客户未配置协议或服务商未配置签章");
                return false;
            }
            batchPayload.setProtocolAndSignatureBO(protocolAndSignature);
        }
        return true;
    }

    /**
     * 准备检查批次中证件信息重复的 map，通过 getDocumentTypeAndNumberString 获取 key，而以 SE 为 value
     *
     * @param batchPayload BP
     * @return true
     */
    protected boolean prepareForDuplicationCheckOfDocument(BP batchPayload) {
        // 证件信息为 key，SE 为 value 存入 map，用于检查批次中证件信息有无重复
        batchPayload.getSingleEntityList().forEach(se -> batchPayload.getDocumentMap().putIfAbsent(getDocumentTypeAndNumberString(se), se));
        return true;
    }

    /**
     * 5.3.0人员重复校验开关，费用发放开关生效，接单导入不生效默认false
     *
     * @param singlePayload
     * @param piecePayload
     * @param batchPayload
     * @return
     */
    protected boolean allowEmployeeRepeat(SP singlePayload, PP piecePayload, BP batchPayload) {
        return false;
    }

    /**
     * 通过证件信息或手机号查询人员：
     * 1、先通过证件号码和证件类型查询人员
     * -人员重复，则验证失败
     * -人员存在、但导入手机号不同且未被使用，则需要更新手机号
     * 2、人员不存在，则通过手机号查询（如果有手机号）
     * -手机号重复，则验证失败
     * -手机号已关联证件号，则验证失败
     *
     * @param singlePayload sp
     * @param piecePayload  pp
     * @param batchPayload  bp
     * @return false 表示查询结果数据异常，做验证失败处理，不再进行后续的处理
     */
    protected boolean findEmployeeByDocumentOrMobile(SP singlePayload, PP piecePayload, BP batchPayload) {
        return
                // 根据证件信息查询用户，检查证件重复，如编辑手机号，则检查手机号是否被使用
                findEmployeeByDocument(singlePayload, batchPayload)

                        // 证件信息不存在时，根据手机号查询用户，检查手机号是否重复、或被他人使用
                        && findEmployeeByMobile(singlePayload, batchPayload);
    }

    /**
     * 根据人员表的查询结果对人员信息进行处理：
     * 1、如果未查询到人员，则新增用户：
     * -如果是身份证，进行三要素验证，三要素失败则该条记录验证失败；三要素验证成功，则生成实名已认证的用户
     * -非身份证，则生成实名审核中的用户
     * 2、如果查询到人员，则检查认证状态：
     * -已存在记录未认证，如果是身份证，则进行三要素校验，三要素失败则该记录验证失败；三要素验证成功，则更新实名信息，且实名已认证
     * -已存在记录未认证，如果是非身份证，则更新实名信息，且实名审核中
     * -已存在记录实名已认证，如果手机号变更，则更新手机号
     * 3、用户已存在，检查该用户在 客户-服务商 下是否已关联：
     * -如果存在，则使用第一条数据（id 排序）
     * -如果重复，发送系统提醒，但不会验证失败
     * 4、用户已存在，检查银行卡是否已存在
     * -如果存在，则使用第一条（id 排序）
     * -如果重复，发送系统提醒，但不会验证失败
     *
     * @param singlePayload sp
     * @param piecePayload  pp
     * @param batchPayload  bp
     * @return false 表示人员信息验证失败
     */
    protected boolean dealWithCoreEmployee(SP singlePayload, PP piecePayload, BP batchPayload) {
        if (singlePayload.getCoreEmployee() == null) {
            if (!isAgeValidated(singlePayload, batchPayload)) {
                return false;
            }
            if (batchPayload.isNeedCheckThreeElement() && DocumentTypeEnum.ID == singlePayload.getDocumentTypeEnum()) {
                if (!isThreeElementSuccess(singlePayload, batchPayload)) {
                    return false;
                }
                // 三要素通过，则实名已认证
                singlePayload.setCoreEmployee(employeeSupportService.renewalEmployeeOfAuthIdentity(newEmployee(singlePayload, batchPayload)));
            } else {
                // 非身份证、或关闭三要素认证，用户待审核
                singlePayload.setCoreEmployee(newEmployee(singlePayload, batchPayload));
                singlePayload.getCoreEmployee().setAuthIdentity(EmployeeAuthenticationStatusEnum.AUTHENTICATING.getCode());
            }
            // 手机号重复且允许创建用户，不保存手机号
            if (!singlePayload.isMobileUnique()) {
                singlePayload.getCoreEmployee().setMobile("");
            }
            singlePayload.getCoreEmployee().setCreateAndUpdateUser(batchPayload.getCoreUser());
            coreEmployeeMapper.insert(singlePayload.getCoreEmployee());
        } else {
            // 数据库已存在，且未认证
            if (!EmployeeAuthenticationStatusEnum.AUTHENTICATED.getCode().equals(singlePayload.getCoreEmployee().getAuthIdentity())) {
                if (!isAgeValidated(singlePayload, batchPayload)) {
                    return false;
                }
                if (batchPayload.isNeedCheckThreeElement() && DocumentTypeEnum.ID == singlePayload.getDocumentTypeEnum()) {
                    if (!isThreeElementSuccess(singlePayload, batchPayload)) {
                        // 已存在用户未认证，且导入数据三要素失败，直接验证失败
                        return false;
                    }
                    // 三要素通过，更新实名信息，且已认证状态
                    employeeSupportService.renewalEmployeeOfIdentityInfo(singlePayload.getCoreEmployee(), singlePayload.getEmployeeName(),
                            singlePayload.getDocumentTypeEnum(), singlePayload.getDocumentNumber(), singlePayload.getBankCard());
                    employeeSupportService.renewalEmployeeOfAuthIdentity(singlePayload.getCoreEmployee());
                    // 已存在 coreEmployee，原实名未认证，本次通过了实名认证，需要更新已存在 employeeCustomer
                    singlePayload.setExistEmployeeIdentityAuthed(true);
                } else {
                    // 已存在用户未认证，且导入信息非身份证，更新用户信息，且状态未审核中
                    singlePayload.getCoreEmployee().setAuthIdentity(EmployeeAuthenticationStatusEnum.AUTHENTICATING.getCode());
                    singlePayload.getCoreEmployee().setAuthIdentityFailReason("");
                    employeeSupportService.renewalEmployeeOfIdentityInfo(singlePayload.getCoreEmployee(), singlePayload.getEmployeeName(),
                            singlePayload.getDocumentTypeEnum(), singlePayload.getDocumentNumber(), singlePayload.getBankCard());
                }
                // 用户已存在，且未认证，子类更新用户表信息
                if (!renewEmployee(singlePayload, batchPayload)) {
                    return false;
                }
                // 手机号编辑在同证件信息查找到用户时已经处理，这里不应该再赋值
                coreEmployeeMapper.updateById(singlePayload.getCoreEmployee());
            } else {
                // 数据库存在且已认证
                if (!singlePayload.getEmployeeName().equals(singlePayload.getCoreEmployee().getUserName())) {
                    // 已存在用户已认证，但导入数据的姓名不一致，导入数据进行三要素校验；处理历史脏数据
                    if (!batchPayload.isNeedCheckThreeElement() || DocumentTypeEnum.ID != singlePayload.getDocumentTypeEnum()) {
                        singlePayload.getErrMsgList().add("已存在相同证件号，姓名不一致");
                        return false;
                    }
                    if (!isThreeElementSuccess(singlePayload, batchPayload)) {
                        return false;
                    } else {
                        // 三要素通过，更新已存在用户的姓名
                        CoreEmployee employee = new CoreEmployee();
                        employee.setId(singlePayload.getCoreEmployee().getId());
                        employee.setUserName(singlePayload.getEmployeeName());
                        if (singlePayload.isMobileModified()) {
                            employee.setMobile(singlePayload.getMobile());
                        }
                        employee.setUpdateUser(batchPayload.getCoreUser());
                        coreEmployeeMapper.updateById(employee);
                        singlePayload.getCoreEmployee().setUserName(singlePayload.getEmployeeName());
                        // 更新关联数据的用户姓名
                        coreUserBankMapper.updateUserNameByUserId(singlePayload.getEmployeeName(), singlePayload.getCoreEmployee().getId());
                        coreEmployeeCustomerMapper.updateEmployeeNameByEmployeeId(singlePayload.getEmployeeName(), singlePayload.getCoreEmployee().getId());
                    }
                } else if (singlePayload.isMobileModified()) {
                    // 已存在数据实名已认证，如果更新了手机号，单独更新
                    coreEmployeeMapper.updateMobileById(singlePayload.getCoreEmployee().getId(), singlePayload.getMobile());
                }
                // coreEmployee 已存在且已认证，子类更新额外信息
                if (!additionalUpdateEmployee(singlePayload, batchPayload)) {
                    return false;
                }
            }
            return
                    // 用户已存在，查找用户-客户
                    findEmployeeCustomer(singlePayload, piecePayload, batchPayload)
                            // 用户已存在，查找 银行卡
                            && findUserBank(singlePayload, piecePayload, batchPayload);
        }
        return true;
    }

    /**
     * employeeCustomer 不存在时查找关联 employeeCustomer
     *
     * @param singlePayload sp
     * @param piecePayload  pp
     * @param batchPayload  bp
     * @return true 表示执行成功；false 表示执行失败
     */
    protected boolean findEmployeeCustomer(SP singlePayload, PP piecePayload, BP batchPayload) {
        if (singlePayload.getCoreEmployeeCustomer() == null) {
            // 用户表已存在的，检查用户-客户表是否存在
            List<CoreEmployeeCustomer> coreEmployeeCustomers = coreEmployeeCustomerMapper.selectList(Condition.<CoreEmployeeCustomer>wrapper()
                    .eq("customer_id", batchPayload.getCoreCustomerChannelMerchant().getCustomerId())
                    .eq("channel_merchant_id", batchPayload.getCoreCustomerChannelMerchant().getChannelMerchantId())
                    .eq("employee_id", singlePayload.getCoreEmployee().getId()));
            if (coreEmployeeCustomers.size() > 0) {
                singlePayload.setCoreEmployeeCustomer(coreEmployeeCustomers.get(0));
                if (coreEmployeeCustomers.size() > 1) {
                    logService.remind("用户-客户表信息重复，id=" + coreEmployeeCustomers.stream()
                            .map(CoreEmployeeCustomer::getId).map(String::valueOf).collect(Collectors.joining(",")));
                }
            }
        }
        return true;
    }

    /**
     * userBank 不存在是查找关联银行卡
     *
     * @param singlePayload sp
     * @param piecePayload  pp
     * @param batchPayload  bp
     * @return true 表示执行成功，false 表示执行失败
     */
    protected boolean findUserBank(SP singlePayload, PP piecePayload, BP batchPayload) {
        if (singlePayload.getCoreUserBank() == null) {
            // 用户表已存在，检查银行卡是否存在
            List<CoreUserBank> coreUserBanks = coreUserBankMapper.selectList(Condition.<CoreUserBank>wrapper()
                    .eq("user_id", singlePayload.getCoreEmployee().getId()).eq("card_no", singlePayload.getBankCard()));
            if (coreUserBanks.size() > 0) {
                singlePayload.setCoreUserBank(coreUserBanks.get(0));
                if (coreUserBanks.size() > 1) {
                    logService.remind("银行卡信息重复：userId=" + singlePayload.getCoreEmployee().getId() + ", cardNo=" + singlePayload.getBankCard());
                }
            }
        }
        return true;
    }

    /**
     * 如果银行卡不存在，且实名已认证，则新增银行卡；如果非借记卡，验证失败
     *
     * @param singlePayload sp
     * @param piecePayload  pp
     * @param batchPayload  bp
     * @return false 表示银行卡信息验证失败
     */
    protected boolean dealWithCoreUserBank(SP singlePayload, PP piecePayload, BP batchPayload) {
        if (singlePayload.getCoreUserBank() == null) {
            if (batchPayload.isNeedCheckThreeElement() && DocumentTypeEnum.ID == singlePayload.getDocumentTypeEnum()) {
                if (isThreeElementSuccess(singlePayload, batchPayload) && isDebitBankCard(singlePayload, batchPayload)) {
                    singlePayload.setCoreUserBank(newUserBank(singlePayload, batchPayload));
                    singlePayload.getCoreUserBank().setBankKey(singlePayload.getBankCardBranchNo());
                    singlePayload.getCoreUserBank().setCreateAndUpdateUser(batchPayload.getCoreUser());
                    coreUserBankMapper.insert(singlePayload.getCoreUserBank());
                }
            }
        }
        return true;
    }

    /**
     * 如果 用户-客户 不存在，创建新的 用户-客户 关联记录，并发起协议；如果已存在，且实名未认证，则更新人员信息和认证状态；如果手机号变更，则更新手机号
     *
     * @param singlePayload sp
     * @param piecePayload  pp
     * @param batchPayload  bp
     * @return false 表示验证失败
     */
    protected boolean dealWithCoreEmployeeCustomer(SP singlePayload, PP piecePayload, BP batchPayload) {
        if (singlePayload.getCoreEmployeeCustomer() == null) {
            // 新增用户、或已存在用户但未查询到用户-客户关联数据
            singlePayload.setCoreEmployeeCustomer(newEmployeeCustomer(singlePayload, batchPayload));
            singlePayload.getCoreEmployeeCustomer().setCreateAndUpdateUser(batchPayload.getCoreUser());
            coreEmployeeCustomerMapper.insert(singlePayload.getCoreEmployeeCustomer());
        } else {
            // 用户-客户 已存在
            if (!EmployeeCustomerAuthStatusEnum.AUTHORIZED.getCode().equals(singlePayload.getCoreEmployeeCustomer().getAuthIdentity())) {
                // 实名未认证，更新实名信息：用户姓名以 coreEmployee 为准，其他几项以 singlePayload 中数据为准
                if (singlePayload.getCoreUserBank() == null) {
                    employeeCustomerSupportService.renewalEmployeeCustomerOfIdentityInfo(singlePayload.getCoreEmployeeCustomer(), singlePayload.getCoreEmployee().getUserName(),
                            singlePayload.getDocumentTypeEnum(), singlePayload.getDocumentNumber(), singlePayload.getBankCard());
                } else {
                    employeeCustomerSupportService.renewalEmployeeCustomerOfIdentityInfo(singlePayload.getCoreEmployeeCustomer(), singlePayload.getCoreEmployee().getUserName(),
                            singlePayload.getDocumentTypeEnum(), singlePayload.getDocumentNumber(), singlePayload.getCoreUserBank());
                }
                if (EmployeeAuthenticationStatusEnum.AUTHENTICATED.getCode().equals(singlePayload.getCoreEmployee().getAuthIdentity())) {
                    employeeCustomerSupportService.renewalEmployeeCustomerOfAuthStatus(singlePayload.getCoreEmployeeCustomer(), singlePayload.getCoreEmployee(),
                            batchPayload.getCoreCustomerChannelMerchant());
                }
                if (singlePayload.isMobileModified()) {
                    singlePayload.getCoreEmployeeCustomer().setRemark1(singlePayload.getMobile());
                }
                // 用户-客户 已存在，且未认证，子类更新额外信息
                if (!renewEmployeeCustomer(singlePayload, batchPayload)) {
                    return false;
                }
                coreEmployeeCustomerMapper.updateById(singlePayload.getCoreEmployeeCustomer());
            } else {
                // 用户-客户 已存在，且已认证
                if (singlePayload.isMobileModified()) {
                    coreEmployeeCustomerMapper.updateMobileById(singlePayload.getCoreEmployeeCustomer().getId(), singlePayload.getMobile());
                }
                if (!additionalUpdateEmployeeCustomer(singlePayload, batchPayload)) {
                    return false;
                }
            }
        }
        // hotfix_0423：已存在 coreEmployee，原实名未认证，本次通过了实名认证，需要更新已存在 employeeCustomer
        if (Boolean.TRUE.equals(singlePayload.getExistEmployeeIdentityAuthed())) {
            List<CoreEmployeeCustomer> identityUnAuthed = coreEmployeeCustomerMapper.selectList(Condition.<CoreEmployeeCustomer>wrapper()
                    .eq("employee_id", singlePayload.getCoreEmployee().getId())
                    .ne("auth_identity", EmployeeCustomerAuthStatusEnum.AUTHORIZED.getCode()));
            if (!identityUnAuthed.isEmpty()) {
                identityUnAuthed.forEach(ec -> {
                    if (singlePayload.getCoreUserBank() == null) {
                        employeeCustomerSupportService.renewalEmployeeCustomerOfIdentityInfo(ec, singlePayload.getCoreEmployee().getUserName(),
                                singlePayload.getDocumentTypeEnum(), singlePayload.getDocumentNumber(), singlePayload.getBankCard());
                    } else {
                        employeeCustomerSupportService.renewalEmployeeCustomerOfIdentityInfo(ec, singlePayload.getCoreEmployee().getUserName(),
                                singlePayload.getDocumentTypeEnum(), singlePayload.getDocumentNumber(), singlePayload.getCoreUserBank());
                    }
                    employeeCustomerSupportService.renewalEmployeeCustomerOfAuthStatus(ec, singlePayload.getCoreEmployee());
                    coreEmployeeCustomerMapper.updateById(ec);
                });
            }
        }
        return true;
    }

    /**
     * 如果 用户-客户 未补签、且未生成待签协议，则发起签约，无论是否已认证
     *
     * @param singlePayload sp
     * @param piecePayload  pp
     * @param batchPayload  bp
     * @return false 表示生成失败
     */
    protected boolean dealWithCoreAgreementDetail(SP singlePayload, PP piecePayload, BP batchPayload) {
        if (singlePayload.getCoreAgreementDetail() == null) {
            // 签约状态 未签约，且补签状态 非已补签
            if (EmployeeSigningStatusEnum.NO.getCode().equals(singlePayload.getCoreEmployeeCustomer().getSigningStatus()) &&
                    !EmployeeSigningAgainStatusEnum.SIGNED.getCode().equals(singlePayload.getCoreEmployeeCustomer().getSigningAgainStatus())) {
                // ProtocolAndSignatureBO 是用户签约的准备信息，批处理时会在批次启动时预加载，单处理时需要签约才加载
                if (batchPayload.getProtocolAndSignatureBO() == null) {
                    // ProtocolAndSignatureBO 如未准备好，addAgreementDetail 将返回 null
                    singlePayload.setCoreAgreementDetail(agreementSigningService.addAgreementDetail(singlePayload.getCoreEmployeeCustomer(), AgreementSigningSourceEnum.MANUAL));
                    if (singlePayload.getCoreAgreementDetail() == null) {
                        singlePayload.getErrMsgList().add("发起签约失败");
                        return false;
                    }
                } else {
                    // ProtocolAndSignatureBO 肯定有效
                    singlePayload.setCoreAgreementDetail(agreementSigningService.addAgreementDetail(singlePayload.getCoreEmployeeCustomer(),
                            batchPayload.getProtocolAndSignatureBO(), AgreementSigningSourceEnum.MANUAL));
                }
            }
        }
        return true;
    }

    /**
     * 返回 ${documentType}-${documentNumber} 格式的字符串为唯一key，用以判断批次内证件信息是否重复
     *
     * @param singleEntity se
     * @return str
     */
    protected abstract String getDocumentTypeAndNumberString(SE singleEntity);

    /**
     * 当前 se 是否已经处理成功过，此检查是为了导入的幂等性，当批次异常中断重启时，已处理过的 se 不会再次执行
     *
     * @param singlePayload se
     * @return true 表示 se 已处理过
     */
    protected abstract boolean isSingleEntityProcessed(SP singlePayload);

    /**
     * 手机号是否必须
     */
    protected boolean isMobileNecessary() {
        return true;
    }

    /**
     * 人员信息验证通过
     *
     * @param singlePayload sp
     * @param piecePayload  pp
     * @param batchPayload  bp
     */
    protected abstract void updateValidateSuccess(SP singlePayload, PP piecePayload, BP batchPayload);

    /**
     * 人员信息验证失败，更新数据表状态
     *
     * @param singlePayload sp
     * @param piecePayload  pp
     * @param batchPayload  bp
     * @param errMsg        失败描述
     */
    protected abstract void updateValidateFailed(SP singlePayload, PP piecePayload, BP batchPayload, String errMsg);


    /**
     * 根据证件信息查询用户，检查证件重复，如编辑手机号，则检查手机号是否被使用
     *
     * @param singlePayload sp
     * @param batchPayload  bp
     * @return true 表示无证件或手机号重复，或无视手机号重复
     */
    protected boolean findEmployeeByDocument(SP singlePayload, BP batchPayload) {
        if (singlePayload.getCoreEmployee() == null) {
            List<CoreEmployee> coreEmployeesByDocument = coreEmployeeMapper.selectList(Condition.<CoreEmployee>wrapper()
                    .eq("document_type", singlePayload.getDocumentTypeEnum().getCode()).eq("document_number", singlePayload.getDocumentNumber()));
            if (coreEmployeesByDocument.size() > 1) {
                singlePayload.getErrMsgList().add(logService.remind("数据库中证件信息重复：" + getDocumentTypeAndNumberString(singlePayload.getSingleEntity())));
                return false;
            } else if (coreEmployeesByDocument.size() == 1) {
                singlePayload.setCoreEmployee(coreEmployeesByDocument.get(0));
                // 变更手机号已被使用，且不允许手机号重复，则返回失败
                return checkMobileModifiable(singlePayload, batchPayload);
            }
        }
        // TODO: 21/6/9 预加载的不会检查手机号更新；后续版本修改：导入手机号变更且重复，人员列表导入拦截，接单和发放不拦截也不更新
        return true;
    }

    /**
     * 证件信息不存在时，根据手机号查询用户，检查手机号是否重复、或被他人使用
     *
     * @param singlePayload sp
     * @param batchPayload  bp
     * @return true 表示无手机号重复，或无视手机号重复
     */
    protected boolean findEmployeeByMobile(SP singlePayload, BP batchPayload) {
        if (singlePayload.getCoreEmployee() == null && StringUtils.isNotEmpty(singlePayload.getMobile())) {
            List<CoreEmployee> coreEmployeesByMobile = coreEmployeeMapper.selectList(Condition.<CoreEmployee>wrapper().eq("mobile", singlePayload.getMobile()));
            if (coreEmployeesByMobile.size() > 1) {
                // 手机号重复且不允许无视手机号重复，则拦截
                if (!batchPayload.isIgnoreMobileRepeated()) {
                    singlePayload.getErrMsgList().add(logService.remind("数据库中手机号重复：" + singlePayload.getMobile()));
                    return false;
                }
                singlePayload.setMobileUnique(false);
            }
            // 手机号存在且证件信息为空，则使用该用户信息；
            if (coreEmployeesByMobile.size() > 0) {
                singlePayload.setCoreEmployee(coreEmployeesByMobile.get(0));
                if (StringUtils.isNotEmpty(singlePayload.getCoreEmployee().getDocumentNumber())) {
                    // 手机号存在且证件信息不为空，如不允许手机号重复，则拦截
                    if (!batchPayload.isIgnoreMobileRepeated()) {
                        singlePayload.getErrMsgList().add("手机号已被他人使用");
                        return false;
                    }
                    // 无视手机号重复，创建新用户，但是不写入手机号
                    singlePayload.setCoreEmployee(null);
                    singlePayload.setMobileUnique(false);
                }
            }
        }
        return true;
    }

    /**
     * 如果修改手机号，检查手机号是否被他人使用
     *
     * @param singlePayload sp
     * @param batchPayload  bp
     * @return true 表示手机号未编辑或可编辑；false 表示编辑了手机号但被他人使用
     */
    protected boolean checkMobileModifiable(SP singlePayload, BP batchPayload) {
        if (StringUtils.isNotEmpty(singlePayload.getMobile()) && !singlePayload.getMobile().equals(singlePayload.getCoreEmployee().getMobile())) {
            // 提交的手机号和当前手机号不同，判断手机号是否被他人使用，未被他人使用则更新，否则验证失败
            if (coreEmployeeMapper.selectCount(Condition.<CoreEmployee>wrapper().eq("mobile", singlePayload.getMobile())) == 0) {
                singlePayload.getCoreEmployee().setMobile(singlePayload.getMobile());
                singlePayload.setMobileModified(true);
            } else if (!batchPayload.isIgnoreMobileRepeated()) {
                // 变更手机号被使用，且不允许无视手机号重复，则拦截；如允许手机号重复，则不拦截，也不保存手机号
                singlePayload.getErrMsgList().add("手机号已被他人使用");
                return false;
            } else {
                // 手机号重复，且没有被拦截
                singlePayload.setMobileUnique(false);
            }
        }
        return true;
    }

    /**
     * 进行三要素校验
     *
     * @param singlePayload sp
     * @param batchPayload  bp
     * @return true 表示校验通过，false 表示验证失败，不再执行后续处理
     */
    protected boolean isThreeElementSuccess(SP singlePayload, BP batchPayload) {
        // TODO: 21/3/2 三要素如果重构的话这一段也可以改成模板方法
        if (singlePayload.getIsThreeElementSuccess() != null) {
            return singlePayload.getIsThreeElementSuccess();
        }
        Map<String, Object> result = threeElementsService.threeElementsVerify(singlePayload.getEmployeeName(), singlePayload.getDocumentNumber(), singlePayload.getBankCard(),
                RequestSourceEnum.PC_LIST_IMPORT.getCode());
        if (null == result || !ExceptionEnum.SUCCESS.getCode().equals(result.get("resultCode"))) {
            singlePayload.getErrMsgList().add("姓名、身份证、银行卡验证失败");
            singlePayload.setThreeElementSuccess(false);
            return false;
        }
        singlePayload.setThreeElementSuccess(true);
        return true;
    }

    /**
     * 检查银行卡是否是借记卡
     *
     * @param singlePayload sp
     * @param batchPayload  bp
     * @return true 表示银行是借记卡，false 表示非借记卡，银行卡导入失败，当前记录验证失败
     */
    protected boolean isDebitBankCard(SP singlePayload, BP batchPayload) {
        Map<String, String> res = epayCallingService.queryBankInfo(singlePayload.getBankCard());
        if (!res.containsKey("cardType") || !"DC".equals(res.get("cardType"))) {
            singlePayload.getErrMsgList().add("银行卡不是借记卡");
            return false;
        } else if (StringUtils.isNotBlank(res.get("branchNo"))) {
            singlePayload.setBankCardBranchNo(res.get("branchNo"));
        }
        return true;
    }

    /**
     * new 一个用户但不保存到数据库
     *
     * @param singlePayload sp
     * @param batchPayload  bp
     * @return 未保存的新用户对象
     */
    protected CoreEmployee newEmployee(SP singlePayload, BP batchPayload) {
        return employeeSupportService.newEmployeeUnverified(singlePayload.getDocumentTypeEnum(), singlePayload.getDocumentNumber(),
                singlePayload.getMobile(), singlePayload.getEmployeeName(), singlePayload.getBankCard(),
                batchPayload.getCoreCustomerChannelMerchant(), batchPayload.getEmployeeRegisterSource());
    }

    /**
     * new 一个银行卡但不保存到数据库
     *
     * @param singlePayload SP
     * @param batchPayload  BP
     * @return 未保存的新银行卡
     */
    protected CoreUserBank newUserBank(SP singlePayload, BP batchPayload) {
        return bankCardSupportService.newUserBank(singlePayload.getCoreEmployee(), singlePayload.getBankCard());
    }

    /**
     * new 一个用户-客户对象但不保存到数据库
     *
     * @param singlePayload SP
     * @param batchPayload  BP
     * @return 未保存的用户-客户
     */
    protected CoreEmployeeCustomer newEmployeeCustomer(SP singlePayload, BP batchPayload) {
        if (singlePayload.getCoreUserBank() == null) {
            return employeeCustomerSupportService.newEmployeeCustomer(singlePayload.getCoreEmployee(),
                    singlePayload.getBankCard(), batchPayload.getCoreCustomerChannelMerchant(), batchPayload.getEmployeeRegisterSource());
        } else {
            return employeeCustomerSupportService.newEmployeeCustomer(singlePayload.getCoreEmployee(),
                    singlePayload.getCoreUserBank(), batchPayload.getCoreCustomerChannelMerchant(), batchPayload.getEmployeeRegisterSource());
        }
    }

    /**
     * 用户已存在，更新额外用户信息，但不用保存到 db
     *
     * @param singlePayload SP
     * @param batchPayload  BP
     * @return true 表示执行成功
     */
    protected boolean renewEmployee(SP singlePayload, BP batchPayload) {
        return true;
    }

    /**
     * 用户-客户已存在，更新额外用户-客户信息，但不用保存到 db
     *
     * @param singlePayload SP
     * @param batchPayload  BP
     * @return true 表示执行成功
     */
    protected boolean renewEmployeeCustomer(SP singlePayload, BP batchPayload) {
        return true;
    }

    /**
     * abs 中通用逻辑未更新 coreEmployee，子类如果需要更新，则需要保存到 db
     *
     * @param singlePayload SP
     * @param batchPayload  BP
     * @return true 表示执行成功
     */
    protected boolean additionalUpdateEmployee(SP singlePayload, BP batchPayload) {
        return true;
    }

    /**
     * abs 中通用逻辑未更新 coreEmployeeCustomer，子类如果需要更新，则需要保存到 db
     *
     * @param singlePayload SP
     * @param batchPayload  BP
     * @return true 表示执行成功
     */
    protected boolean additionalUpdateEmployeeCustomer(SP singlePayload, BP batchPayload) {
        return true;
    }

    /**
     * V5.3.2根据客户的配置判断人员的年龄是否超限
     *
     * @param singlePayload SP
     * @param batchPayload  BP
     * @return true 表示符合年龄限制，false 表示年龄超限
     */
    protected boolean isAgeValidated(SP singlePayload, BP batchPayload) {
        Integer age = DateUtil.getAgeByDocument(singlePayload.getDocumentNumber());
        if (age < batchPayload.getCoreCustomerChannelMerchant().getAgeStart() ||
                age > batchPayload.getCoreCustomerChannelMerchant().getAgeEnd()) {
            singlePayload.getErrMsgList().add("年龄必须在【" + batchPayload.getCoreCustomerChannelMerchant().getAgeStart() + "-" + batchPayload.getCoreCustomerChannelMerchant().getAgeEnd() + "】周岁");
            return false;
        }
        return true;
    }
}
